// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Contains integration tests for the generated `Convert` implementations.
//!
//! For generated code, we write hand-crafted integration tests *once* in the
//! project. The generated implementations for
//! `gaxi::prost::{FromProto, ToProto}` usually reference a type generated by
//! Protobuf, and these are `pub(crate)`. Therefore, the tests must be part of
//! the crate that contains them, and cannot be a separate integration tests
//! crate, or a file in the `tests/` directory.
//!
//! This fixture tests conversions of `google::longrunning::Operation`s.
//!
//! Other tests are found in `src/firestore/src/convert.rs`.

use crate::google;
use gaxi::prost::{ConvertError, FromProto, ToProto};
// This would fail if storage admin introduces LROs. This is unlikely as
// one of the motivations for `StorageControl` was to separate the LROs.
// Worst case, we can skip the offending functions until we have fixed
// this code to support them.
use crate::generated::gapic_control::transport::{lro_any_from_prost, lro_any_to_prost};

impl ToProto<google::longrunning::Operation> for longrunning::model::Operation {
    type Output = google::longrunning::Operation;
    fn to_proto(
        self,
    ) -> std::result::Result<google::longrunning::Operation, gaxi::prost::ConvertError> {
        use google::longrunning::operation::Result as P;
        use longrunning::model::operation::Result as M;
        let metadata = self.metadata.map(lro_any_to_prost).transpose()?;
        #[warn(clippy::wildcard_enum_match_arm)]
        let result = self
            .result
            .map(|result| match result {
                M::Error(status) => status.to_proto().map(P::Error),
                M::Response(any) => lro_any_to_prost(*any).map(P::Response),
                _ => unreachable!(),
            })
            .transpose()?;

        Ok(google::longrunning::Operation {
            name: self.name,
            metadata,
            done: self.done,
            result,
        })
    }
}

impl FromProto<longrunning::model::Operation> for google::longrunning::Operation {
    fn cnv(self) -> std::result::Result<longrunning::model::Operation, ConvertError> {
        use google::longrunning::operation::Result as P;
        use longrunning::model::operation::Result as M;
        let metadata = self.metadata.map(lro_any_from_prost).transpose()?;
        let result = self
            .result
            .map(|result| match result {
                P::Error(status) => status.cnv().map(|s| M::Error(s.into())),
                P::Response(any) => lro_any_from_prost(any).map(|r| M::Response(r.into())),
            })
            .transpose()?;

        Ok(longrunning::model::Operation::new()
            .set_name(self.name)
            .set_or_clear_metadata(metadata)
            .set_done(self.done)
            .set_result(result))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use test_case::test_case;

    fn prost_folder() -> prost_types::Any {
        let folder = google::storage::control::v2::Folder {
            name: "test-name".to_string(),
            metageneration: 42,
            ..Default::default()
        };
        prost_types::Any::from_msg(&folder).unwrap()
    }

    fn wkt_folder() -> wkt::Any {
        let folder = crate::model::Folder::new()
            .set_name("test-name")
            .set_metageneration(42);
        wkt::Any::from_msg(&folder).unwrap()
    }

    fn prost_create_metadata() -> prost_types::Any {
        let md = google::storage::control::v2::CreateAnywhereCacheMetadata {
            zone: Some("test-zone".to_string()),
            ..Default::default()
        };
        prost_types::Any::from_msg(&md).unwrap()
    }

    fn wkt_create_metadata() -> wkt::Any {
        let md = crate::model::CreateAnywhereCacheMetadata::new().set_zone("test-zone".to_string());
        wkt::Any::from_msg(&md).unwrap()
    }

    fn wkt_lro_with_metadata() -> longrunning::model::Operation {
        longrunning::model::Operation::new()
            .set_name("name")
            .set_metadata(wkt_create_metadata())
    }

    fn prost_lro_with_metadata() -> google::longrunning::Operation {
        google::longrunning::Operation {
            name: "name".to_string(),
            metadata: Some(prost_create_metadata()),
            ..Default::default()
        }
    }

    fn wkt_lro_with_response() -> longrunning::model::Operation {
        longrunning::model::Operation::new()
            .set_name("name")
            .set_done(true)
            .set_response(wkt_folder())
    }

    fn prost_lro_with_response() -> google::longrunning::Operation {
        google::longrunning::Operation {
            name: "name".to_string(),
            done: true,
            result: Some(google::longrunning::operation::Result::Response(
                prost_folder(),
            )),
            ..Default::default()
        }
    }

    fn wkt_lro_with_error() -> longrunning::model::Operation {
        longrunning::model::Operation::new()
            .set_name("name")
            .set_done(true)
            .set_error(rpc::model::Status::new().set_code(5))
    }

    fn prost_lro_with_error() -> google::longrunning::Operation {
        google::longrunning::Operation {
            name: "name".to_string(),
            done: true,
            result: Some(google::longrunning::operation::Result::Error(
                google::rpc::Status {
                    code: 5,
                    ..Default::default()
                },
            )),
            ..Default::default()
        }
    }

    #[test_case(Default::default(), Default::default(); "empty LROs")]
    #[test_case(prost_lro_with_metadata(), wkt_lro_with_metadata(); "with metadata")]
    #[test_case(prost_lro_with_response(), wkt_lro_with_response(); "with response")]
    #[test_case(prost_lro_with_error(), wkt_lro_with_error(); "with error")]
    fn lro_roundtrip(
        prost: google::longrunning::Operation,
        wkt: longrunning::model::Operation,
    ) -> anyhow::Result<()> {
        assert_eq!(prost, wkt.clone().to_proto()?);
        assert_eq!(wkt, prost.cnv()?);
        Ok(())
    }

    #[test]
    fn lro_to_prost_unknown_metadata() -> anyhow::Result<()> {
        let wkt = longrunning::model::Operation::new()
            .set_metadata(wkt::Any::from_msg(&wkt::Duration::default())?);
        let prost = wkt.to_proto();
        assert!(matches!(prost, Err(ConvertError::UnexpectedTypeUrl(_))));
        Ok(())
    }

    #[test]
    fn lro_to_prost_unknown_response() -> anyhow::Result<()> {
        let wkt = longrunning::model::Operation::new()
            .set_response(wkt::Any::from_msg(&wkt::Duration::default())?);
        let prost = wkt.to_proto();
        assert!(matches!(prost, Err(ConvertError::UnexpectedTypeUrl(_))));
        Ok(())
    }

    #[test]
    fn lro_from_prost_unknown_metadata() -> anyhow::Result<()> {
        let unknown = prost_types::Any::from_msg(&prost_types::Duration::default())?;
        let prost = google::longrunning::Operation {
            metadata: Some(unknown),
            ..Default::default()
        };
        let wkt = prost.cnv();
        assert!(matches!(wkt, Err(ConvertError::UnexpectedTypeUrl(_))));
        Ok(())
    }

    #[test]
    fn lro_from_prost_unknown_response() -> anyhow::Result<()> {
        let unknown = prost_types::Any::from_msg(&prost_types::Duration::default())?;
        let prost = google::longrunning::Operation {
            result: Some(google::longrunning::operation::Result::Response(unknown)),
            ..Default::default()
        };
        let wkt = prost.cnv();
        assert!(matches!(wkt, Err(ConvertError::UnexpectedTypeUrl(_))));
        Ok(())
    }

    #[test_case(prost_types::Any::default(), wkt::Any::default(); "default Any")]
    #[test_case(prost_folder(), wkt_folder(); "Any with LRO response type")]
    #[test_case(prost_create_metadata(), wkt_create_metadata(); "Any with LRO metadata type")]
    fn lro_any_roundtrip(prost: prost_types::Any, wkt: wkt::Any) -> anyhow::Result<()> {
        assert_eq!(prost, lro_any_to_prost(wkt.clone())?);
        assert_eq!(wkt, lro_any_from_prost(prost)?);
        Ok(())
    }

    #[test]
    fn lro_any_to_prost_unknown_type() -> anyhow::Result<()> {
        let wkt = wkt::Any::from_msg(&wkt::Duration::default())?;
        let prost = lro_any_to_prost(wkt);
        assert!(matches!(prost, Err(ConvertError::UnexpectedTypeUrl(_))));
        Ok(())
    }

    #[test]
    fn lro_any_from_prost_unknown_type() -> anyhow::Result<()> {
        let unknown = prost_types::Duration::default();
        let prost = prost_types::Any::from_msg(&unknown)?;
        let wkt = lro_any_from_prost(prost);
        assert!(matches!(wkt, Err(ConvertError::UnexpectedTypeUrl(_))));
        Ok(())
    }
}
